package com.jihong.util;

import lombok.Data;
import lombok.Getter;
import net.sf.jsqlparser.JSQLParserException;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.ExpressionVisitorAdapter;
import net.sf.jsqlparser.expression.JdbcParameter;
import net.sf.jsqlparser.expression.StringValue;
import net.sf.jsqlparser.expression.operators.relational.*;
import net.sf.jsqlparser.parser.CCJSqlParserManager;
import net.sf.jsqlparser.schema.Column;
import net.sf.jsqlparser.statement.Statement;
import net.sf.jsqlparser.statement.StatementVisitorAdapter;
import net.sf.jsqlparser.statement.delete.Delete;
import net.sf.jsqlparser.statement.insert.Insert;
import net.sf.jsqlparser.statement.select.AllColumns;
import net.sf.jsqlparser.statement.select.Select;
import net.sf.jsqlparser.statement.select.SelectItem;
import net.sf.jsqlparser.statement.update.Update;
import net.sf.jsqlparser.statement.update.UpdateSet;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.reflection.MetaObject;

import java.io.StringReader;
import java.util.ArrayList;
import java.util.List;

@Data
public class SqlParse {
    static final String PLACEHOLDER = "?";
    String oldSql;
    String tableName;
    ParamMappingHandler paramMappingHandler;
    List<MyConfig.EncryptData> restoreColumn;
    SqlParseHelper sqlParse;
    Statement stmt;
    MetaObject metaObject;


    public String getUpdateSql(){
        if (stmt != null){
            return stmt.toString();
        }
        return oldSql;
    }

    public SqlParse(String sql, List<ParameterMapping> parameterMappingList, List<MyConfig.EncryptData> restoreColumn,MetaObject metaObject) {
        this.oldSql = sql;
        this.paramMappingHandler = new ParamMappingHandler(parameterMappingList);
        this.metaObject = metaObject;
        this.restoreColumn = restoreColumn;
        CCJSqlParserManager parser = new CCJSqlParserManager();
        try {
            this.stmt = parser.parse(new StringReader(sql));
            sqlParse = new SqlParseHelper(paramMappingHandler, restoreColumn,metaObject);
            stmt.accept(sqlParse);
        } catch (JSQLParserException e) {
            e.printStackTrace();
        }
    }
    public List<ParameterMapping> getNeedEncryptParameterMapping(){
        return paramMappingHandler.encryptParameterMapping;
    }


    @Getter
    static class SqlParseHelper extends StatementVisitorAdapter {
        ParamMappingHandler paramMappingHandler;
        List<String> selectItemList;
        List<MyConfig.EncryptData> storeColumn;
        MetaObject metaObject;

        private boolean isFirst = true;

        public SqlParseHelper(ParamMappingHandler paramMappingHandler,List<MyConfig.EncryptData> storeColumn,MetaObject metaObject) {
            this.paramMappingHandler = paramMappingHandler;
            this.selectItemList = new ArrayList<>();
            this.storeColumn = storeColumn;
            this.metaObject = metaObject;
        }



        /**
         * 替换 update 占位符
         *
         * @param update
         */
        @Override
        public void visit(Update update) {
            //TODO 检查 set中是否合法，包含加密字段 _en 就直接抛异常
            List<UpdateSet> updateSetList = new ArrayList<>();
            //提取set
            for (UpdateSet updateSet : update.getUpdateSets()) {

                ExpressionList<Column> columns = updateSet.getColumns();
                String columnName = columns.get(0).getColumnName();
                String value = updateSet.getValue(0).toString();
                if (!PLACEHOLDER.equals(value)){
                    continue;
                }
                ParameterMapping parameterMapping = paramMappingHandler.getOneParameterMapping();
                MyConfig.EncryptData encryptData = MyConfig.getEncryptData(storeColumn, columnName);
                if (encryptData != null){
                    String encryptColumn = EncryptHandler.encryptColumn(columnName);
                    //如果为false，那么需要不能替代该字段，表示存量数据还没同步完
                    boolean doubleWrite = encryptData.isDoubleWrite();
                    if (!doubleWrite){
                        columns.get(0).setColumnName(encryptColumn);
                        paramMappingHandler.addEncryptParameterMapping(parameterMapping);
                    }else {
                        List<String> encryptColumnList = new ArrayList<>();
                        String property = parameterMapping.getProperty();
                        Object v = metaObject.getValue(property);
                        encryptColumnList.add( "'" + EncryptHandler.encryptValue(String.valueOf(v)) + "'");
                        updateSetList.add(buildUpdateSet(encryptColumn,encryptColumnList));
                    }
                }
            }
            for (UpdateSet updateSet : updateSetList) {
                update.addUpdateSet(updateSet);
            }
            WhereExpressionVisitor expressionVisitorAdapter = new WhereExpressionVisitor(paramMappingHandler, storeColumn);
            // 提取WHERE条件占位符
            update.getWhere().accept(expressionVisitorAdapter);
        }

        public UpdateSet buildUpdateSet(String columnStr,List<String> value){
            UpdateSet updateSet = new UpdateSet();
            Column column = new Column();
            column.setColumnName(columnStr);
            updateSet.add(column);
            ExpressionList<Expression> expressions = new ExpressionList<>();
            for (String s : value) {
                Column column1 = new Column();
                column1.setColumnName(s);
                expressions.add(column1);
            }
            updateSet.setValues(expressions);
            return updateSet;
        }


        /**
         * 替换 Select 占位符
         *
         * @param select
         */
        @Override
        public void visit(Select select) {
            List<Column> columns = new ArrayList<>();
            for (SelectItem<?> selectItem : select.getPlainSelect().getSelectItems()) {
                Expression expression = selectItem.getExpression();
                if (expression instanceof AllColumns) {
                    selectItemList.add("*");
                } else if (expression instanceof Column) {
                    Column column = (Column) expression;
                    String columnName = column.getColumnName();
                    if (storeColumn.contains(columnName)){
                        String encryptColumn = EncryptHandler.encryptColumn(columnName);
                        column.setColumnName(encryptColumn);
                    }
                    selectItemList.add(columnName);
                }

            }
            for (Column column : columns) {
                select.getPlainSelect().addSelectItem(column);

            }
            Expression where = select.getPlainSelect().getWhere();
            if (where == null){
                return;
            }
            select.getPlainSelect().getWhere().accept(new WhereExpressionVisitor(paramMappingHandler, storeColumn));
        }

        /**
         * 替换Delete的占位符
         *
         * @param delete
         */
        @Override
        public void visit(Delete delete) {
            delete.getWhere().accept(new WhereExpressionVisitor(paramMappingHandler, storeColumn));
        }

        /**
         * 替换insert的占位符
         *
         * @param insert
         */
        @Override
        public void visit(Insert insert) {
            //TODO 检查 insert into 中是否合法，包含加密字段 _en 就直接抛异常
            ExpressionList<Column> columns = insert.getColumns();
            //获取需要加密的字段
            ExpressionList<?> expressions = insert.getValues().getExpressions();
            processInsert(columns, expressions);



        }

        private void processInsert(ExpressionList<Column> columns, Expression expression) {
            if (expression instanceof ParenthesedExpressionList){
                ParenthesedExpressionList parenthesedExpressionList = (ParenthesedExpressionList) expression;
                List<StringValue> stringValueList = new ArrayList<>();
                for (int i = 0; i < parenthesedExpressionList.size(); i++) {
                    Object o = parenthesedExpressionList.get(i);
                    if (o instanceof JdbcParameter){
                        ParameterMapping parameterMapping = paramMappingHandler.getOneParameterMapping();
                        Column column = columns.get(i);
                        String columnName = column.getColumnName();
                        if (!isFirst){
                            columnName = EncryptHandler.decryptColumn(columnName);
                        }
                        MyConfig.EncryptData encryptData = MyConfig.getEncryptData(storeColumn, columnName);
                        if (encryptData == null){
                            continue;
                        }
                        String encryptColumn = EncryptHandler.encryptColumn(columnName);
                        String property = parameterMapping.getProperty();
                        property = metaObject.findProperty(property, true);
                        if (property == null){
                            continue;
                        }
                        Object value = metaObject.getValue(property);
                        if (value == null){
                            continue;
                        }
                        if (encryptData.isDoubleWrite()){
                            if (isFirst){
                                Column enColumn = new Column();
                                enColumn.setColumnName(encryptColumn);
                                columns.add(enColumn);
                                isFirst = false;
                            }
                            String encryptValue = EncryptHandler.encryptValue(String.valueOf(value));
                            stringValueList.add(new StringValue(encryptValue));
                        }else {
                            if (isFirst){
                                column.setColumnName(encryptColumn);
                                isFirst = false;
                            }
                            paramMappingHandler.addEncryptParameterMapping(parameterMapping);
                        }
                    }
                }
                for (StringValue stringValue : stringValueList) {
                    parenthesedExpressionList.addExpression(stringValue);
                }
            }else if (expression instanceof ExpressionList){
                ExpressionList<Expression> expressionList = (ExpressionList) expression;
                for (Expression o : expressionList) {
                    processInsert(columns,o);
                }
            }
        }
    }


    public static class WhereExpressionVisitor extends ExpressionVisitorAdapter {
        ParamMappingHandler paramMappingHandler;
        List<MyConfig.EncryptData> storeColumn;

        public WhereExpressionVisitor(ParamMappingHandler paramMappingHandler,List<MyConfig.EncryptData> storeColumn) {
            this.paramMappingHandler = paramMappingHandler;
            this.storeColumn = storeColumn;
        }


        @Override
        public void visit(EqualsTo expr) {
            processComparison(expr);
        }


        private void processComparison(ComparisonOperator expr) {
            if (!PLACEHOLDER.equals(expr.getRightExpression().toString())) {
                return;
            }
            String column = expr.getLeftExpression().toString();
            ParameterMapping parameterMapping = paramMappingHandler.getOneParameterMapping();
            //判断当前字段是否需要加密
            if (!needEncrypt(column)){
                return;
            }
            //添加需要加密字段的 ParameterMapping
            paramMappingHandler.addEncryptParameterMapping(parameterMapping);
            if (storeColumn != null) {
                for (MyConfig.EncryptData encryptData : storeColumn) {
                    String field = encryptData.getField();
                    boolean doubleWrite = encryptData.isDoubleWrite();
                    if (column.equals(field) && !doubleWrite){
                        String encryptColumn = EncryptHandler.encryptColumn(column);
                        Column leftExpression = expr.getLeftExpression(Column.class);
                        leftExpression.setColumnName(encryptColumn);
                    }
                }
            }

        }

        private boolean needEncrypt(String column) {
            MyConfig.EncryptData encryptDataVerify = MyConfig.getEncryptData(storeColumn, column);
            if (encryptDataVerify == null){
                return false;
            }
            return !encryptDataVerify.isDoubleWrite();
        }




        @Override
        public void visit(Between expr) {
            String column = expr.getLeftExpression().toString();

            String start = expr.getBetweenExpressionStart().toString();
            String end = expr.getBetweenExpressionEnd().toString();
            int count = 0;
            if (PLACEHOLDER.equals(start)) {
                count++;
            }
            if (PLACEHOLDER.equals(end)) {
                count++;
            }
            List<ParameterMapping> parameterMappingList = paramMappingHandler.getParameterMappingList(count);
            if (!needEncrypt(column)){
                return;
            }
            paramMappingHandler.addAllEncryptParameterMapping(parameterMappingList);
        }

        @Override
        public void visit(GreaterThan expr) {
            processComparison(expr);
        }

        @Override
        public void visit(MinorThan expr) {
            processComparison(expr);
        }

        @Override
        public void visit(MinorThanEquals expr) {
            processComparison(expr);
        }

        @Override
        public void visit(GreaterThanEquals expr) {
            processComparison(expr);
        }

        @Override
        public void visit(InExpression expr) {
            String column = expr.getLeftExpression().toString();
            List<ParameterMapping> parameterMappingList = new ArrayList<>();
            ParenthesedExpressionList<Expression> rightExpression = (ParenthesedExpressionList) expr.getRightExpression();
            for (Expression expression : rightExpression) {
                if (expression instanceof JdbcParameter) {
                    parameterMappingList.add(paramMappingHandler.getOneParameterMapping()) ;
                }
            }
            if (!needEncrypt(column)){
                return;
            }
            paramMappingHandler.addAllEncryptParameterMapping(parameterMappingList);
        }
    }
}